///----------------------------------------------------------------------------
/// File Name: View.cs
/// 
/// Description: 
/// 
/// 
/// History:
/// 08 Mar 2004	Aaron Clauson	Created.
/// 12 Jun 2004	Aaron Clauson	Debugged after compiling with v1.1 of framework.
///								Behaviour of XSL transform contained a breaking
///								change. 
///	08 Nov 2004	Aaron Clauson	Added some formatting in the final XML stream.
///	11 Dec 2004	Aaron Clauson	Added speical parameter parsing to redirect urls.
/// 28 Jul 2005	Fergal Keane	Added ex.Message to logger info for transform error.
/// 16 Oct 2008 Guillaume       modified the setting for the XLS Transform to cope with W3C requirements
///
/// License:
/// Public Domain.
///----------------------------------------------------------------------------

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using MaverickLite.Config;
using log4net;

namespace MaverickLite.View
{
	/// <summary>
	/// Views do the work of actually rendering the model.
	/// </summary>
	public abstract class ViewProduct
	{
		protected XslTransform[] Transforms;
		protected static string PhysicalApplicationPath;

		public abstract void Go(object model, HttpResponse output);
	}

	public class ViewFactory
	{
		private const string ATTR_VIEW_NAME = "name";
		private const string ATTR_VIEW_TYPE = "type";
		private const string ATTR_VIEW_PATH = "path";
		private const string REDIRECT_VIEW_NAME = "redirect";
		private const string XML_VIEW_NAME = "xml";
		
		public static ViewProduct CreateView(XmlElement viewElement)
		{
			if(viewElement == null)
			{
				MavAppState.logger.Error("The view factory was passed a null view element.");
				throw new ApplicationException("The view factory was passed a null view element.");
			}

			XmlNode viewType = viewElement.Attributes.GetNamedItem(ATTR_VIEW_TYPE);
			
			if(viewType == null || viewType.Value == null || viewType.Value.Trim() == String.Empty)
			{
				return new XmlView(viewElement);
			}
			else if(Regex.Match(viewType.Value, REDIRECT_VIEW_NAME).Success)
			{
				XmlNode viewPath = viewElement.Attributes.GetNamedItem(ATTR_VIEW_PATH);

				if(viewPath != null)
				{
					return new RedirectView(viewPath.Value);
				}
				else
				{
					return new RedirectView(null);
				}
			}
			else if(Regex.Match(viewType.Value, XML_VIEW_NAME).Success)
			{
				return new XmlView(viewElement);
			}
			else
			{
				throw new ApplicationException("The view " + viewType.Value + " does not exist.");
			}
		}
	}


	public class XmlView : ViewProduct
	{	
		private const string TRANSFORM_NODE_NAME = "transform";
		private const string TRANSFORM_NODE_PATH = "path";
		private const string VIEW_CONTENT_TYPE = "text/html";
		private const string XML_CONTENT_TYPE = "text/xml";
		private const string FINAL_STAGEOUTPUT_KEY = "finalstagexml";	

		private static ILog logger = MavAppState.logger;

		private static bool m_finalStageOutputIsXML = true;		// If set to false the last stage will not be treated as an XML document, 
																// this lets things like a HTML document be produced with < signs in javascript.

		static XmlView()
		{
			PhysicalApplicationPath = MavAppState.PhysicalApplicationPath;
		}


		public XmlView(XmlElement initElement)
		{
			//MavAppState.logger.Info("Creating XmlView.");

			// Add the xml documents from the controller initialisation nodes to the model.
			if(initElement != null)
			{
				XmlNode finalStageAtt = initElement.Attributes.GetNamedItem(FINAL_STAGEOUTPUT_KEY);
				if(finalStageAtt != null && finalStageAtt.Value == "false")
				{
					m_finalStageOutputIsXML = false;
				}
				
				//MavAppState.logger.Info(initElement.OuterXml);
				XmlNodeList transformNodes = initElement.SelectNodes(TRANSFORM_NODE_NAME);
				
				if(transformNodes != null && transformNodes.Count > 0)
				{
					Transforms = new XslTransform[transformNodes.Count];

					// Add the paths to the hashtable in reverse so that the model gets built using the top node first.
					for(int transformNodesCount=0; transformNodesCount<transformNodes.Count; transformNodesCount++)
					{
						XmlNode transformPathNode = transformNodes[transformNodesCount].Attributes.GetNamedItem(TRANSFORM_NODE_PATH);	

						if(transformPathNode != null)
						{
							string transformPath = PhysicalApplicationPath + transformPathNode.Value;
							if(!File.Exists(transformPath))
							{
								throw new ApplicationException("The path specified in the transform node does not exist or is inaccessible, " + transformPath + ".");
							}
								
							XslTransform transformStage = new XslTransform();
							
							try
							{
								transformStage.Load(transformPath);
							}
							catch (Exception ex)
							{
								MavAppState.logger.Error("The transform file, " + transformPath + " could not be loaded, probably invalid XML. (ex = " + ex.Message + ")");
								throw new ApplicationException("The transform file, " + transformPath + " could not be loaded, probably invalid XML.");
							}
								
							Transforms[transformNodesCount] = transformStage;
							//MavAppState.logger.Info("Transform stage added, " + transformPath + ".");
						}
						else
						{
							throw new ApplicationException("A transform element did not specify a path.");
						}
					}
				}
				else
				{
					//MavAppState.logger.Debug("No transform nodes exist for view node.");
				}
			}
		}


		public override void Go(object model, HttpResponse output)
		{
			output.ContentType = VIEW_CONTENT_TYPE;
			
			XmlDocument modelDom = model as XmlDocument;

			//MavAppState.logger.Debug("View stage input model: " + modelDom.OuterXml);

			// XML doms incur a significant overhead, if performance is critical adjust this code section.
			MemoryStream stageOutput = new MemoryStream();			
			XmlDocument xmlStageOutput = modelDom;					// In case there are no transforms defined.
			XPathNavigator stageInput = modelDom.CreateNavigator();

			MemoryStream finalStageOutput = new MemoryStream();

            // Stream writer if a formatted XML output is required.
            //XmlTextWriter xw = new XmlTextWriter(finalStageOutput, Encoding.UTF8);
            //xw.Formatting = MavAppState.OutputFormatting;
            //xw.Indentation = 4;

            XmlWriterSettings stg = new XmlWriterSettings();
            stg.ConformanceLevel = ConformanceLevel.Document;
            stg.Encoding = Encoding.UTF8;
            stg.Indent = true;
            stg.IndentChars = "   ";

            XmlWriter xw = XmlTextWriter.Create(finalStageOutput, stg);

			if(Transforms != null && Transforms.Length > 0)
			{
				for(int index = 0; index<Transforms.Length; index++)
				{
					XslTransform transformStage = Transforms[index];

					//MavAppState.logger.Debug("Commencing transform stage.");
					
					//StreamWriter sw = new StreamWriter(Request.OutputStream, new UTF8Encoding(false));

					stageOutput = new MemoryStream();

					try
					{
						transformStage.Transform(stageInput, null, stageOutput, null);					
					}
					catch(Exception tranExcp)
					{
						MavAppState.logger.Error("The XSL transform in the View stage threw an exception. " + tranExcp.Message);
					}
					
					/*
					// Used to debug after Microsoft changed the behaviour of the Transform method from v1.0 to v1.1.
					stageOutput.Position = 0;
					StreamReader sr = new StreamReader(stageOutput);
					string stageOutputStr = sr.ReadToEnd();
					MavAppState.logger.Debug("Transform stage output: " + stageOutputStr);*/
			
					if(index == Transforms.Length - 1 && !m_finalStageOutputIsXML)
					{
						// Last transform not required to be XML write straight to output.
						finalStageOutput = stageOutput;
						break;
					}
					else
					{
						try
						{
							stageOutput.Position = 0;
							xmlStageOutput.Load(stageOutput);
						}
						catch(Exception stageLoadExcp)
						{
							MavAppState.logger.Debug("An exception occurred trying to load the results of a transform stage into an XML document.");
							throw new ApplicationException("An exception occurred trying to load the results of a transform stage into an XML document. " + stageLoadExcp.Message);
						}	

						if(index == Transforms.Length - 1)
						{
							// Last transform, write to output as nicely formatted XML.
							xmlStageOutput.WriteTo(xw);
							xw.Flush();

							break;
						}
						else
						{
							stageInput = xmlStageOutput.CreateNavigator();
						}

						//}
						//MavAppState.logger.Debug("Transform stage completed.");
					}
				}
			}

			if(stageOutput == null || stageOutput.Length == 0)
			{
				// No result from the transforms or no transforms so just write the model.
				if(xmlStageOutput != null)
				{
					output.ContentType = XML_CONTENT_TYPE;
					xmlStageOutput.WriteTo(xw);
					xw.Flush();
				}
				else
				{
					throw new ApplicationException("Error in the View transform stage, no model and no transform result.");
				}
			}

			finalStageOutput.Position = 0;
			finalStageOutput.WriteTo(output.OutputStream);

			logger.Debug("Request finished: " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff") + ".");

			output.End();
		}
	}

	public class RedirectView : ViewProduct
	{
		protected static Hashtable m_specialParameters = MavAppState.SpecialParameters;	// Special parameters for model, e.g. ${path}.

		private static ILog logger = MavAppState.logger;

		private string m_redirectUrl = null;
		
		public RedirectView(string url)
		{
			//MavAppState.logger.Info("Creating RedirectView: " + url);

			m_redirectUrl = url;
		}
		
		public override void Go(object model, HttpResponse output)
		{
			string redirectUrl = model as string;

			if(redirectUrl != null && redirectUrl.Trim() != String.Empty)
			{
				foreach(DictionaryEntry specialParameter in m_specialParameters)
				{
					redirectUrl = Regex.Replace(redirectUrl, Regex.Escape((string)specialParameter.Key), (string)specialParameter.Value);
				}

				logger.Debug("Redirect view redirecting to " + redirectUrl);
				
				output.Redirect(redirectUrl);	
			}
			else if(m_redirectUrl != null && m_redirectUrl.Trim() != String.Empty)
			{
				foreach(DictionaryEntry specialParameter in m_specialParameters)
				{
					m_redirectUrl = Regex.Replace(m_redirectUrl, Regex.Escape((string)specialParameter.Key), (string)specialParameter.Value);
				}

				logger.Debug("Redirect view redirecting to " + m_redirectUrl);

				output.Redirect(m_redirectUrl);
			}
			else
			{
				MavAppState.logger.Error("Redirect view does not have a null or empty redirection path.");
				throw new HttpException("Redirect view does not have a null or empty redirection path.");
			}
		}
	}
}
